#ifndef __DEVICE_H__
#define __DEVICE_H__

#include <stdexcept>
#include <vector>
#include <boost/cstdint.hpp>
#include <boost/noncopyable.hpp>
#include <boost/thread.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <boost/thread/locks.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/smart_ptr.hpp>

using boost::uint8_t;
using boost::uint16_t;
using boost::int16_t;
using std::size_t;
using std::vector;
using boost::shared_ptr;
namespace device {

struct exception : public std::exception {};
struct Sample {
    double* _data;
    size_t _size;
    Sample(double* data_, size_t size_) : _data(data_), _size(size_) {}
    double operator[](uint8_t ii) const { return _data[ii]; }
    size_t size() const { return _size; }
};

struct not_initialized : public exception {
    const char* what() { return "slot not initialized"; };
};

template<typename Val>
struct Property {
    typedef Val result_type;
    Val _val;
    bool initialized;
    Property() : initialized(false) {}
    Val operator()() const { 
        if(!initialized) throw(not_initialized());
        return _val; 
    }
    void set(Val val) {
        _val=val;
        initialized=true;
    }
};

template<typename Val>
struct SignalingProperty : public Property<Val>, private boost::noncopyable {
    boost::signals2::signal<void (const Val)> signal;
    void set(Val val) { 
        if(!this->initialized || val != this->_val) {
            signal(val);
            this->_val=val;
        }
        this->initialized=true;
    }
};

struct Device : private boost::noncopyable {
    boost::signals2::signal<void (Sample)> sample;
    // status 
    SignalingProperty<bool> sync;
    SignalingProperty<bool> battery;
    SignalingProperty<uint16_t> sampling_rate;
    vector< shared_ptr< SignalingProperty<bool> > > button;
    vector< shared_ptr< SignalingProperty<bool> > > channel_ok;
    Device(size_t buttons, size_t channels) : 
        button(buttons), channel_ok(channels) {
        for(size_t ii=0; ii<buttons; ++ii)
            button[ii]=shared_ptr< SignalingProperty<bool> >(new SignalingProperty<bool>);
        for(size_t ii=0; ii<channels; ++ii)
            channel_ok[ii]=shared_ptr< SignalingProperty<bool> >(new SignalingProperty<bool>);
    }
};

template<typename T>
struct Recorder {
    T* data;
    size_t samples, iisample;
    vector<uint8_t> channels;

    template<typename channel_iterator>
    Recorder(channel_iterator begin, channel_iterator end, int samples_) : 
        samples(samples_), iisample(0), channels(begin, end) {
        data=new T[channels.size()*samples];
    }
    ~Recorder() {
        delete[] data;
    }
    void sampleReceived(const Sample& sample) {
        if(iisample >= samples) throw(std::range_error("out of space in sample buffer"));
        for(size_t ii=0; ii<channels.size(); ++ii) 
            data[iisample*channels.size() + ii]=sample[channels[ii]];
        ++iisample;
    }
    void syncChanged(bool sync) {
        if(sync) return;
        for(size_t ii=0; ii<channels.size(); ++ii)
            data[iisample*channels.size() + ii]=std::numeric_limits<T>::quiet_NaN();
        ++iisample;
    }
    double at(unsigned channel,  unsigned sample) {
        if(channel >= channels.size()) throw(std::range_error("channel out of bounds"));
        if(sample >= samples) throw(std::range_error("sample out of bounds"));
        int ii=sample*channels.size()+channel;
        return data[ii];
    }
};

}; // namespace device

#endif
